#include "laurin-discover-network-clients-module.h"
#include "laurin-utils.h"
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <sys/time.h>
#include <sys/select.h>
#include <libnet.h>




/* redefine the arp header */
typedef struct laurin_arp_hdr
{
	u_int16_t htype;		/* ARP Hardware Format */
	u_int16_t ptype;		/* ARP Protocol Format */
	u_char hlen;			/* ARP Hardware Address Length */
	u_char plen;			/* ARP Protocol Address Length */
	u_int16_t op;			/* ARP Opcode */
	u_char sha[ETH_ALEN];	/* ARP Sender Ethernet Address */
	u_char spa[IP_ALEN];	/* ARP Sender IP Address */
	u_char tha[ETH_ALEN];	/* ARP Target Ethernet Address */
	u_char tpa[IP_ALEN];	/* ARP Target IP Address */
} laurin_arp_header;



void processFrameExtForClients(u_char* arg, const struct pcap_pkthdr *header,const u_char *data)
{
	((LaurinDiscoverNetworkClientsModule*)arg)->processFrame(header, data);
}


LaurinDiscoverNetworkClientsModule::LaurinDiscoverNetworkClientsModule(const Glib::ustring& ifaceName): LaurinModule(Glib::ustring("LAURIN DISCOVER NETWORK CLIENTS"))
{
	_device = new LaurinNetworkDevice(ifaceName);
	_timeout = 1; 		// 1 seconds
}


LaurinDiscoverNetworkClientsModule::~LaurinDiscoverNetworkClientsModule()
{
	delete _device;
}


bool LaurinDiscoverNetworkClientsModule::isInitialized()
{
	return _device->isInitialized();
}


int LaurinDiscoverNetworkClientsModule::getFrameTimeout() const
{
	return _timeout;
}


void LaurinDiscoverNetworkClientsModule::setFrameTimeout(int millisecond)
{
	_timeout = millisecond;
}


LaurinIPNetwork* LaurinDiscoverNetworkClientsModule::getNetwork() const
{
	return _device->getNetwork();
}


void LaurinDiscoverNetworkClientsModule::run()
{

	LaurinIPRange* range = _device->getNetwork()->getNetworkIPRange();
	_device->getNetwork()->clear();
	bool cont = true;


	Glib::ustring filter = Glib::ustring("arp dst ").append(getReadableIP(_device->getIP()));
	if(!_device->setCaptureFilter(filter, _device->getNetwork()->getNetworkSubnetMask()))
		return;

	// write resources
	u_int32_t ip = _device->getIP();
	u_int8_t* mac = _device->getMAC();
	libnet_t* icontext = _device->getInjectionContext();
	u_int32_t dst_ip = -1;
	libnet_ptag_t arp = 0;


 	// read resources
    fd_set fds;
    pcap_t* ccontext = _device->getCaptureContext();
    int sd  = pcap_get_selectable_fd(ccontext);
    struct timeval tv;
    tv.tv_sec = _timeout;
    tv.tv_usec = 0;

    // ?!?
    if( sd < 0)
    	return;


    for(int i = 0; i < range->getNumbersOfIPAddress() && cont; i++)
	{

		dst_ip = range->getCurrentIPAddress();

		FD_ZERO(&fds);				// clear fd set
		FD_SET(sd, &fds);			// add the descriptor to the set
		tv.tv_sec = _timeout;
		tv.tv_usec = 0;

		if(arp = libnet_autobuild_arp(ARPOP_REQUEST, mac, (u_int8_t*) &ip, eth_xmas, (u_int8_t*) &dst_ip, icontext))
		{
			if(libnet_autobuild_ethernet(eth_xmas, ETHERTYPE_ARP, icontext))
			{
				// write packet
				libnet_write(icontext);

				// wait responses
				if(select( sd + 1, &fds, NULL, NULL, &tv) > 0)
				{
					pcap_dispatch(ccontext, 1, processFrameExtForClients, (u_char*) this);
				}

			}

		}

		range->next();
		libnet_clear_packet(icontext);

		cont = !isStopRequestPending();
	}

    delete range;
}


void LaurinDiscoverNetworkClientsModule::innerStop()
{

	pcap_breakloop(_device->getCaptureContext());
}


void LaurinDiscoverNetworkClientsModule::processFrame(const struct pcap_pkthdr *header,const u_char *data)
{

	// mac hdr + arp frame
	if(header->caplen < 42)
		return ;


	struct ether_header* eh = NULL;
	eh = (struct ether_header *) data;

	struct laurin_arp_hdr* ah = NULL;

	char* tmp = (char*)eh;
	ah = (struct laurin_arp_hdr*) & tmp[sizeof(*eh)];


	if(ntohs(ah->op) == ARPOP_REPLY && ntohs(ah->htype) == ARPHRD_ETHER && ntohs(ah->ptype) == 0x0800)
	{

		u_int32_t* t = (u_int32_t*)&ah->spa;
		LaurinNetworkClient client(*t, (u_int8_t*)eh->ether_shost);

		_device->getNetwork()->addClient(client);
	}

}

